<?php

/**
 * Classe de gestion des correspondances de mapping
 *
 * @author Eric Colinet (Zend)
 */
// ZFR-ECO : Définir dans le modèle abstrait les méthodes : chercher, charger,
// enregistrer afin que l'implémentation soit identique pour tous
// ZFR-ECO : Définir le role et le mode opératoire des méthode :
// ZFR-ECO : - charger remplit les attributs de l'objet en cours
// ZFR-ECO : - enregistrer fait persister les donnée de l'objet en cours
// ZFR-ECO : - chercher renvoit un tableau d'objet de modèle
// ZFR-ECO : - lister* renvoient un tableau d'objet de modèle en fonction d'une
// régle métier
// ZFR-ECO : Pour aller plus loin il est possible de déclarer deux classes de
// Mapper supplémentaires :
// ZFR-ECO : - Model_Mapper_Table pour l'accès aux tables : concrétise "charger"
// et autres ds le cas d'un accès à la base de donnée
// ZFR-ECO : - Model_Mapper_WebService idem mais dans le cas d'un service web
// ZFR-ECO : Les classes de mappers peuvent donc hériter de l'une ou l'autre en
// fonction du besoin
abstract class Jnd_Model_Mapper_Abstract
{
    
    /**
     * Dictonnaire Attribut => Nom de champs
     *
     * @var $_dict array
     */
    protected $_dict = array();
    protected $_dictDate = array();
    
    /**
     *
     * @var $_table Zend_Db_Table_Abstract
     */
    protected $_table = null;

    public function __construct()
    {
        if (! isset($this->_dict['id'])) {
            throw new Jnd_Exception("Un mapper doit toujours avoir une clef 'id' (" . get_class($this) . ")<br />" . Zend_Debug::dump($this->_dict));
        }
    }

    /**
     * Transforme les nom des attribut de modèle en nom de champs DB
     *
     * @param array $data            
     * @return array
     */
    public function map( array $data )
    {
        $result = array();
        foreach ($data as $k => $v) {
            if (isset($this->_dict[$k])) {
                $result[$this->_dict[$k]] = $v;
            } else {
                // $result[$k] = $v;
                throw new Jnd_Exception("L'attribut '$v' n'est pas défini dans le dictionnaire");
            }
        }
        return $result;
    }

    /**
     * Effectue le map sur un tableau d'enregistrement
     *
     * @param array $datas            
     */
    public function mapArray( array $datas )
    {
        $result = array();
        foreach ($datas as $data) {
            $result[] = $this->map($data);
        }
        return $result;
    }

    /**
     * Transforme les nom de champs DB en nom d'attribut de modèle
     *
     * @param array $data            
     * @return array
     */
    public function unmap( array $data )
    {
        $result = array();
        foreach ($data as $k => $v) {
            $new_k = array_search($k, $this->_dict);
            if ($new_k !== false) {
                $result[$new_k] = $v;
            } else {
                throw new Jnd_Exception("L'attribut '$k' n'est pas défini dans le dictionnaire");
            }
        }
        return $result;
    }

    /**
     * Effectue le unmap sur un tableau d'enregistrement
     *
     * @param array $datas            
     */
    public function unmapArray( array $datas )
    {
        $result = array();
        foreach ($datas as $data) {
            $result[] = $this->unmap($data);
        }
        return $result;
    }

    /**
     * Renvoit le nom de la table associée au modèle
     *
     * @param Jnd_Model_Abstract $object            
     * @return string
     */
    protected function _getTableName(/*$object*/)
    {
        $tableClass = str_replace('_Model_Mapper_', '_Model_Table_', get_class($this));
        if (! class_exists($tableClass)) {
            throw new Jnd_Exception("Table '$tableClass' inexistante");
        }
        return $tableClass;
    }

    /**
     * Renvoit l'objet Table associé au modèle
     *
     * @return Zend_Db_Table_Abstract
     */
    public function _getTable( /*$object*/ )
    {
        if ($this->_table === null) {
            $tableClass = $this->_getTableName(/*$object*/);
            $this->_table = new $tableClass();
        }
        return $this->_table;
    }

    /**
     * Renvoit la classe du model associé au mapper
     *
     * @return String
     */
    protected function _getModelName()
    {
        $modelClass = str_replace('_Model_Mapper_', '_Model_', get_class($this));
        if (! class_exists($modelClass)) {
            throw new Jnd_Exception("Modèle '$modelClass' inexistante");
        }
        return $modelClass;
    }

    /**
     * Renvoit un objet Model associé au mapper
     *
     * @return Jnd_Model_Abstract
     */
    protected function _getNewModel( $datas = null )
    {
        $modelClass = $this->_getModelName();
        return new $modelClass($datas);
    }

    /**
     * Retourne un tableau avec en attriburs les champs du model et en valeur
     * les valeurs chargées dans le model.
     *
     * @deprecated Utiliser la méthode getDataFromDict()
     * @author desjardins
     * @param Jnd_Model_Abstract $object            
     * @return array
     */
    protected function listeAttributsCharges( /*Jnd_Model_Abstract*/ $object )
    {
        return $this->getDataFromDict($object);
    }

    /**
     * Retourne le dictionnaire
     *
     * @return array
     */
    public function getDict( $name = null )
    {
        if (is_null($name)) {
            return $this->_dict;
        } else if (isset($this->_dict[$name])) {
            return $this->_dict[$name];
        } elseif (in_array($name, $this->_dict)) {
            return array_search($name, $this->_dict); // @todo Eric -> n'est
                                                          // ce pas une erreur ?
        } else {
            throw new Jnd_Exception("L'attribut '$name' n'est pas dans le dictionnaire");
        }
    }

    /**
     * Renvoie le nombre d'éléments de la table
     *
     * @param Jnd_Model_Abastract $objetModel            
     * @return int
     */
    public function compter( /*$objetModel*/ )
    {
        $table = $this->_getTable(/*$objetModel*/);
        $select = new Zend_Db_Table_Select($table);
        $select->from($table, 'COUNT(*) AS num');
        $resultatRequete = $table->fetchRow($select);
        return (int) $resultatRequete->num;
    }

    public function lister( $tri = null, $decalage = 0, $nombre = Jnd_Global_Constantes::PAGINATOR_TAILLE_DEFAUT )
    {
        if (! is_int($nombre) || ! is_int($decalage)) {
            throw new Exception("lister exige que le nombre ou le décalage soient des entiers");
        }
        $listes = array();
        $table = $this->_getTable(/*$objetModel*/);
        $select = new Zend_Db_Table_Select($table);
        if (is_string($tri) && ! empty($tri) && (isset($this->_dict[$tri]) || isset($this->_dict[substr($tri, 1)]))) {
            $order = 'ASC';
            if ($tri[0] == '-') {
                $tri = substr($tri, 1);
                $order = 'DESC';
            }
            $select->order($this->getDict($tri) . " " . $order);
        } else if (is_array($tri)) { // listerqq
            $champs = isset($tri['champ']) ? $tri['champ'] : 'id';
            $ordre = isset($tri['sens']) ? $tri['sens'] : 'DESC';
            $select->order($this->getDict($champs) . ' ' . $ordre);
        }
        if ($decalage != 0 and $nombre != 0) {
            $select->limitPage($decalage, $nombre);
        }
        $resultatRequete = $table->fetchAll($select);
        $donneesUnMapees = $this->unmapArray($resultatRequete->toArray());
        $results = array();
        foreach ($donneesUnMapees as $donnees) {
            $results[] = $this->_getNewModel($donnees);
        }
        return $results;
    }

    public function listerqq( $decalage, $nombre, $ordre = null )
    {
        return $this->lister($ordre, $decalage, $nombre);
    }
    
    // public function listerqq( $decalage, $nombre, $objetModel, $ordre = null
    // ) {
    //
    // if( is_int($nombre) && is_int($decalage) ) {
    // $liste = array();
    // $table = $this->_getTable($objetModel);
    // $select = new Zend_Db_Table_Select($table);
    // if( is_null($ordre) || ! isset($this->_dict[$ordre['champ']]) ) {
    // $select->order(array(
    // $this->_dict['Id'] . " DESC"
    // ));
    // } else {
    // $select->order(array(
    // $this->_dict[$ordre['champ']] . " " . $ordre['sens']
    // ));
    // }
    // If( $decalage != 0 && $nombre != 0 ) {
    // $select->limitPage($decalage, $nombre);
    // }
    // // die($select);
    // $resultatRequete = $table->fetchAll($select);
    // $resultatRequete = $resultatRequete->toArray();
    // foreach ( $resultatRequete as $elementTableau ) {
    // $elementTableau = $this->unmap($elementTableau);
    // $class = get_class($objetModel);
    // $listes[] = new $class($elementTableau);
    // }
    // // Zend_Debug::dump($listes);die();
    // return $listes;
    // } else {
    // throw new Exception("listerqq exige que le nombre ou le décalage soient
    // des entiers");
    // }
    // }
    /**
     *
     * @deprecated Utiliser chercheChamps
     * @param unknown_type $idCherche            
     * @param unknown_type $champRetour            
     * @param unknown_type $objetModel            
     */
    public function recuperationStockage( $idCherche, $champRetour, $objetModel )
    {
        return $this->chercheChamps($idCherche, $champRetour, $objetModel);
    }

    /**
     * Récupère l'attribut spécifié du modèle donné
     *
     * @param mixed $idCherche            
     * @param string $champRetour            
     * @param Jnd_Model_Abstract $objetModel            
     */
    public function chercheChamps( $idCherche, $champRetour, $objetModel )
    {
        $objetModel->chercher($idCherche);
        $methode = $champRetour;
        return $objetModel->$methode;
    }

    /**
     * TODO Renommer chercher en charger
     *
     * @param unknown_type $id            
     * @param unknown_type $objetModel            
     */
    public function charger( $id/* = 0*/, $objetModel = null )
    {
        return $this->chercher($id, $objetModel);
    }

    /**
     * Cherche l'enregistrement d'id $id
     *
     * @param mixed $id            
     * @return mixed
     */
    public function chercher( $id/* = 0*/, $objetModel = null )
    {
        if (! is_null($objetModel)) {
            $objetModel->initialise();
        }
        $table = $this->_getTable(/*$objetModel*/);
        $results = $table->find($id)->toArray();
        if (count($results) == 0) {
            return is_null($objetModel) ? array() : false;
        }
        if (is_null($objetModel)) {
            return $this->unmap($results[0]);
        }
        $donnees = $this->unmap($results[0]);
        $donnees = $this->traitementDicoSpecifique($donnees);
        $objetModel->setOptions($donnees);
        return true;
    }

    public function getDictNumeric()
    {
        return $this->_dictNumeric;
    }

    public function getDictDate()
    {
        return $this->_dictDate;
    }

    protected function traitementDicoSpecifique( $donnees = array() )
    {
        foreach ($this->getDictDate() as $champModel => $champStockage) {
            if (isset($donnees[$champModel]) && ($donnees[$champModel] != "" && $donnees[$champModel] != "0000-00-00") && strpos($donnees[$champModel], "-") !== FALSE) {
                $tmp = $donnees[$champModel];
                if (strpos($tmp, ":") === FALSE) {
                    // On est dans un format court.
                    list ($y, $m, $d) = preg_split('[-]', $tmp);
                    $dateFormate = date(Jnd_Global_Constantes::FORMAT_DATE_WEB_COURT, mktime(0, 0, 0, intval($m), intval($d), intval($y)));
                } else {
                    // On est dans un format long
                    $tmp = str_replace(array(
                        " ", 
                        ":"
                    ), array(
                        "-", 
                        "-"
                    ), $tmp);
                    list ($y, $m, $d, $h, $min, $s) = preg_split('[-]', $tmp);
                    $dateFormate = date(Jnd_Global_Constantes::FORMAT_DATE_WEB_LONG, mktime(intval($h), intval($min), intval($s), intval($m), intval($d), intval($y)));
                }
                $donnees[$champModel] = $dateFormate;
            } elseif (isset($donnees[$champModel]) && $donnees[$champModel] == '0000-00-00') {
                $donnees[$champModel] = "";
            }
        }
        return $donnees;
    }

    /**
     * Récupert les valeurs des attributs d'un model et les mets dans un tableau
     * d'attribut en fonction du dictionnaire
     *
     * @param Jnd_Model_Abstract $objetModel            
     * @return array
     */
    protected function getDataFromDict( Jnd_Model_Abstract $objetModel )
    {
        $dict = $this->getDict();
        foreach ($dict as $attribut => $champBase) {
            $methode = 'get' . ucfirst($attribut);
            $resultat = $objetModel->$methode();
            if (is_array($resultat)) {
                $resultat = implode(';', $resultat);
            }
            $result[$attribut] = $resultat;
        }
        
        $result = $this->getDataFromDictArray($result);
        
        return $result;
    }

    protected function getDataFromDictArray( $result )
    {
        if (is_array($this->_dictDate) && count($this->_dictDate) > 0) {
            foreach ($this->_dictDate as $attribut => $champBase) {
                $dateFormate = null;
                if (! empty($result[$attribut]) && strpos($result[$attribut], ":") === false) {
                    list ($d, $m, $y) = preg_split('[/]', $result[$attribut]);
                    $dateFormate = date(Jnd_Global_Constantes::FORMAT_DATE_BASE_COURT, mktime(0, 0, 0, intval($m), intval($d), intval($y)));
                } elseif (! empty($result[$attribut])) {
                    $data = str_replace(array(
                        " ", 
                        ":"
                    ), array(
                        "/", 
                        "/"
                    ), $result[$attribut]);
                    // echo "<br />$attribut '$data' dans " . get_class($this);
                    list ($d, $m, $y, $h, $min, $s) = preg_split('[/]', $data);
                    $dateFormate = date(Jnd_Global_Constantes::FORMAT_DATE_BASE, mktime(intval($h), intval($min), intval($s), intval($m), intval($d), intval($y)));
                }
                
                $result[$attribut] = $dateFormate;
            }
        }
        return $result;
    }

    public function sauver( $objetModel )
    {
        $donnees = $this->getDataFromDict($objetModel);
        $donnees = $this->map($donnees);
        $table = $this->_getTable();
        $primary = $table->info(Zend_Db_Table::PRIMARY);
        $clePrimaire = $primary[1];
        $clePrimaireValeur = $donnees[$clePrimaire];
        unset($donnees[$clePrimaire]);
        unset($donnees['']); // On supprime les données ici des tables donc la
                             // clé primaires n'est pas l'id
        if (! empty($clePrimaireValeur)) { /* update */
            return $table->update($donnees, array(
                "$clePrimaire = ?" => $clePrimaireValeur
            ));
        } else {
            /* Gestion du NOT NULL DEFAULT VALUE */
            foreach ($donnees as $donnee => $value) {
                if (is_null($value)) {
                    unset($donnees[$donnee]);
                }
            }
            $lastInsertId = $table->insert($donnees);
            $this->chercher($lastInsertId, $objetModel);
            return $lastInsertId;
        }
    }

    public function supprimer( $id )
    {
        $table = $this->_getTable();
        $clePrimaire = $this->_dict['id'];
        if ($id > 0) { /* delete */
            return $table->delete(array(
                "$clePrimaire = ?" => $id
            ));
        }
    }

    /**
     * Construit un tableau composé d'une chaine séparé
     * par un caractère redéfinissable
     *
     * @param string $data            
     * @param string $separator            
     * @return array (clef=>clef)
     */
    public function dataToArray( $data, $separator = ';' )
    {
        $result = array();
        $collection = explode($separator, $data);
        foreach ($collection as $item) {
            $result[$item] = $item;
        }
        return $result;
    }

    /**
     * Méthode à surcharger dans le mapper dans le cas d'une utilisation d'un
     * DataGrid
     *
     * @param array $values            
     * @param mixed $environnement            
     * @param string $identite            
     * @param array $options            
     * @return Zend_Db_Table_Select
     */
    public function getListSelect( $values, $environnement, $identite, $options = null )
    {
        throw new Jnd_Exception(get_class($this) . ' ' . __("n'implemente pas getListSelect"));
    }

    public function recuperationValeurParId( $id, $valeur )
    {
        $table = $this->_getTable();
        $select = new Zend_Db_Table_Select($table);
        $primary = $table->info(Zend_Db_Table::PRIMARY);
        $select->where($primary[1] . "=?", $id);
        $stmt = $select->query();
        $resultat = $stmt->fetch();
        return $resultat[$valeur];
    }

    public function recuperationIdParValeur( $valeur, $champ = "nom" )
    {
        $table = $this->_getTable();
        $select = new Zend_Db_Table_Select($table);
        $select->where($this->getDict($champ) . "=?", $valeur);
        $stmt = $select->query();
        $resultat = $stmt->fetch();
        return $resultat[$this->getDict("id")];
    }
}
